﻿///////////////////////////////////////////////////////////////////////////////
//
//  This source is part of the  "Managed Task Framework.NET Runtime"  (MTF.NET Runtime).
//
//
//  Copyright (c) 2011, Multicore Consulting Limited (UK)
//  All rights reserved.
//  http://www.multicoreconsulting.co.uk
//
//  Redistribution and use in source and binary forms, with or without
//  modification, are permitted provided that the following conditions
//  are met:
//
//  -  Redistributions of source code must retain the above copyright
//     notice, this list of conditions and the following disclaimer.
//  -  Redistributions in binary form must reproduce the above copyright
//     notice, this list of conditions and the following disclaimer in
//     the documentation and/or other materials provided with the
//     distribution.
//  -  Neither the name of Multicore Consulting Limited nor the names of
//     its contributors may be used to endorse or promote products
//     derived from this software without specific prior written
//     permission.
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
//  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
//  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
//  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
//  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
//  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
//  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
//  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
//  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
//  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
//  POSSIBILITY OF SUCH DAMAGE.
//
///////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MTFOrg.MTF.MTFRuntime;
using System.Collections;

namespace MTFOrg.MTF.MTFCore
{
	/// <summary>
	/// Helper class for manipulating connection information.
	/// </summary>
	class ConnectionHelper
	{
		/// <summary>
		/// Holds calculated state for a connection that is expensive to recompute each time
		/// </summary>
		protected class CachedConnectionState
		{
            /// <summary>
            /// Calculated number of consumer links in the connection.
            /// </summary>
            private int m_NumConsumerLinks;

            /// <summary>
            /// Calculated number of producer links in the connection.
            /// </summary>
            private int m_NumProducerLinks;

            /// <summary>
            /// Start link number for the producer end of this connection.
            /// </summary>
            private int m_ProducerStartLink;

            /// <summary>
            /// Start link number for the consumer end of this connection.
            /// </summary>
            private int m_ConsumerStartLink;

            /// <summary>
            /// Map of links from producer to consumer for each element.
            /// </summary>
            private int[,,] m_ProducerLinkMap;

            /// <summary>
            /// Map of links from consumer to producer for each element.
            /// </summary>
            private int[,,] m_ConsumerLinkMap;

            /// <summary>
            /// Ordered list of connections of which this connection is a member.
            /// </summary>
            private ConnectionList m_OrderedConnections;

            /// <summary>
            /// Mapping of links from unordered links to ordered links.
            /// </summary>
            private int[] m_ToOrderedLinkMap;

            /// <summary>
            /// Mapping of links from ordered links to unordered links.
            /// </summary>
            private int[] m_FromOrderedLinkMap;

            /// <summary>
            /// Connection on other side of gap between ProducerConsumer and specific object.
            /// </summary>
            private IConnection m_CorrespondingConnection;

            /// <summary>
            /// Flag specifying whether the corresponding connection relays outside the local task scope.
            /// </summary>
            private bool m_RelayExternal;

            /// <summary>
            /// Connection mapping class.
            /// </summary>
            private ProducerConsumer.IConnectionMapping m_ConnectionMapping;

            /// <summary>
            /// Represents an unknown number of links.
            /// </summary>
			const int NUM_LINKS_UNKNOWN = -1;

            /// <summary>
            /// Represents an unknown start link.
            /// </summary>
            const int START_LINK_UNKNOWN = -1;

            /// <summary>
            /// Gets or sets the connection mapping class.
            /// </summary>
            public ProducerConsumer.IConnectionMapping ConnectionMapping
            {
                get { return m_ConnectionMapping; }
                set { m_ConnectionMapping = value; }
            }

            /// <summary>
            /// Gets whether the connection mapping class has been cached.
            /// </summary>
            public bool ConnectionMappingIsCached
            {
                get { return m_ConnectionMapping != null; }
            }

            /// <summary>
            /// Gets or sets the calculated number of consumer links in the connection.
            /// </summary>
            public int NumConsumerLinks
            {
                get { return m_NumConsumerLinks; }
                set { m_NumConsumerLinks = value; }
            }

            /// <summary>
            /// Gets whether number of consumer links has been cached.
            /// </summary>
            public bool NumConsumerLinksIsCached
            {
                get { return m_NumConsumerLinks != NUM_LINKS_UNKNOWN; }
            }

            /// <summary>
            /// Gets or sets the calculated number of producer links in the connection.
            /// </summary>
            public int NumProducerLinks
            {
                get { return m_NumProducerLinks; }
                set { m_NumProducerLinks = value; }
            }

            /// <summary>
            /// Gets whether number of producer links has been cached.
            /// </summary>
            public bool NumProducerLinksIsCached
            {
                get { return m_NumProducerLinks != NUM_LINKS_UNKNOWN; }
            }

            /// <summary>
            /// Gets or sets the start link for the producer end of this connection.
            /// </summary>
            public int ProducerStartLink
            {
                get { return m_ProducerStartLink; }
                set { m_ProducerStartLink = value; }
            }

            /// <summary>
            /// Gets whether the start link for the producer end of this connection is cached.
            /// </summary>
            public bool ProducerStartLinkIsCached
            {
                get { return m_ProducerStartLink != START_LINK_UNKNOWN; }
            }

            /// <summary>
            /// Gets or sets the start link for the consumer end of this connection.
            /// </summary>
            public int ConsumerStartLink
            {
                get { return m_ConsumerStartLink; }
                set { m_ConsumerStartLink = value; }
            }

            /// <summary>
            /// Gets whether the start link for the consumer end of this connection is cached.
            /// </summary>
            public bool ConsumerStartLinkIsCached
            {
                get { return m_ConsumerStartLink != START_LINK_UNKNOWN; }
            }

            /// <summary>
            /// Gets whether the producer link map has been cached.
            /// </summary>
            public bool ProducerLinkMapIsCached
            {
                get { return m_ProducerLinkMap != null; }
            }

            /// <summary>
            /// Gets whether the producer link map has been cached.
            /// </summary>
            public int[,,] ProducerLinkMap
            {
                get { return m_ProducerLinkMap; }
                set { m_ProducerLinkMap = value; }
            }

            /// <summary>
            /// Gets whether the consumer link map has been cached.
            /// </summary>
            public bool ConsumerLinkMapIsCached
            {
                get { return m_ConsumerLinkMap != null; }
            }

            /// <summary>
            /// Gets whether the consumer link map has been cached.
            /// </summary>
            public int[,,] ConsumerLinkMap
            {
                get { return m_ConsumerLinkMap; }
                set { m_ConsumerLinkMap = value; }
            }

			/// <summary>
			/// Gets or sets the ordered list of connections of which this connection is a member.
			/// </summary>
			public ConnectionList OrderedConnections
			{
				get { return m_OrderedConnections; }
				set { m_OrderedConnections = value; }
			}

			/// <summary>
			/// Gets whether the ordered connection list has been cached.
			/// </summary>
			public bool OrderedConnectionsAreCached
			{
				get { return m_OrderedConnections != null; }
			}

			/// <summary>
			/// Gets or sets the to-ordered link map.
			/// </summary>
			public int[] ToOrderedLinkMap
			{
				get { return m_ToOrderedLinkMap; }
				set { m_ToOrderedLinkMap = value; }
			}

			/// <summary>
			/// Gets or sets the from-ordered link map.
			/// </summary>
			public int[] FromOrderedLinkMap
			{
				get { return m_FromOrderedLinkMap; }
				set { m_FromOrderedLinkMap = value; }
			}

			/// <summary>
			/// Gets whether the ordered link map is cached.
			/// </summary>
			public bool OrderedLinkMapsAreCached
			{
				get { return m_ToOrderedLinkMap != null; }
			}

            /// <summary>
            /// Gets or sets the corresponding connection associated with this connection.
            /// </summary>
            public IConnection CorrespondingConnection
            {
                get { return m_CorrespondingConnection; }
                set { m_CorrespondingConnection = value; }
            }

            /// <summary>
            /// Gets or sets the flag specifying whether this connection relays externally.
            /// </summary>
            public bool RelayExternal
            {
                get { return m_RelayExternal; }
                set { m_RelayExternal = value; }
            }

			/// <summary>
			/// Constructor.
			/// </summary>
			public CachedConnectionState()
			{
                m_NumConsumerLinks = NUM_LINKS_UNKNOWN;
                m_NumProducerLinks = NUM_LINKS_UNKNOWN;
                m_ProducerStartLink = START_LINK_UNKNOWN;
                m_ConsumerStartLink = START_LINK_UNKNOWN;
                m_OrderedConnections = null;
				m_ToOrderedLinkMap = null;
				m_FromOrderedLinkMap = null;
                m_CorrespondingConnection = null;
                m_ProducerLinkMap = null;
                m_ConsumerLinkMap = null;
                m_ConnectionMapping = null;
			}
		}

        /// <summary>
        /// Default mapping class instance.
        /// </summary>
        static protected ProducerConsumer.ConnectionMapping m_DefaultMapping = new ProducerConsumer.SimpleConnectionMapping();

		/// <summary>
		/// Gets the cached connection state, creating a new cache object the first time.
		/// </summary>
		/// <param name="connection">Connection to retrieve cached state for</param>
		/// <returns>Cached state for given connection</returns>
		protected static CachedConnectionState GetCachedConnectionState(IConnection connection)
		{
			CachedConnectionState cachedConnectionState = connection.Tag as CachedConnectionState;
			if (cachedConnectionState == null)
			{
				cachedConnectionState = new CachedConnectionState();
				connection.Tag = cachedConnectionState;
			}

			return cachedConnectionState;
		}

        /// <summary>
        /// Gets the cached connection size from the consumer perspective
        /// or calculates it if it isn't cached yet.
        /// </summary>
        /// <param name="connection">Connection to retrieve size of</param>
        /// <returns>Size of given connection</returns>
        public static int GetConsumerConnectionSize(IConnection connection)
        {
            // Check to see if num-links was cached previously
            CachedConnectionState cachedConnectionState = GetCachedConnectionState(connection);
            if (cachedConnectionState.NumConsumerLinksIsCached)
                return cachedConnectionState.NumConsumerLinks;

            // Calculate num-links
            ProducerConsumer.IConnectionMapping connectionMapping = GetMappingClass(connection);
            int[] consumerCxnDims = connectionMapping.GetConsumerConnectionDimensions();

            // Set num links in cache and return
            cachedConnectionState.NumConsumerLinks = DimensionHelper.GetNumElementsFromDimensions(consumerCxnDims);
            return cachedConnectionState.NumConsumerLinks;
        }

        /// <summary>
        /// Gets the cached connection size from the producer perspective
        /// or calculates it if it isn't cached yet.
        /// </summary>
        /// <param name="connection">Connection to retrieve size of</param>
        /// <returns>Size of given connection</returns>
        public static int GetProducerConnectionSize(IConnection connection)
        {
            // Check to see if num-links was cached previously
            CachedConnectionState cachedConnectionState = GetCachedConnectionState(connection);
            if (cachedConnectionState.NumProducerLinksIsCached)
                return cachedConnectionState.NumProducerLinks;

            // Calculate num-links
            ProducerConsumer.IConnectionMapping connectionMapping = GetMappingClass(connection);
            int[] producerCxnDims = connectionMapping.GetProducerConnectionDimensions();

            // Set num links in cache and return
            cachedConnectionState.NumProducerLinks = DimensionHelper.GetNumElementsFromDimensions(producerCxnDims);
            return cachedConnectionState.NumProducerLinks;
        }

		/// <summary>
		/// Gets the list of ordered connections that this connection is a member of.
		/// </summary>
		/// <param name="connection">Connection in the ordered list</param>
		/// <returns>Ordered list of connections</returns>
		public static IEnumerable GetOrderedConnections(IConnection connection)
		{
			// Check to see if the ordered connection list was cached previously
			CachedConnectionState cachedConnectionState = GetCachedConnectionState(connection);
			if (cachedConnectionState.OrderedConnectionsAreCached)
				return cachedConnectionState.OrderedConnections;

			// Build up ordered connection list
			ConnectionList orderedConnections = new ConnectionList();
			IConnectionPoint consumerConnectionPoint = connection.SourceConnectionPoint;
			foreach (IConnection consumerConnection in consumerConnectionPoint.Connections)
			{
				if (orderedConnections.Count == 0)
					orderedConnections.Add(consumerConnection);
				else
				{
					ConnectionProperty orderingProperty = consumerConnection.GetProperty("Ordering");
					int consumerConnectionOrder = orderingProperty != null ? (int)orderingProperty.Value : 0;
					int i = 0;
					foreach (IConnection orderedConnection in orderedConnections)
					{
						ConnectionProperty orderedOrderingProperty = orderedConnection.GetProperty("Ordering");
						int orderedConnectionOrder = orderedOrderingProperty != null ? (int)orderedOrderingProperty.Value : 0;
						if (orderedConnectionOrder >= consumerConnectionOrder)
							break;
						i++;
					}
					orderedConnections.Insert(i, consumerConnection);
				}
			}

			// Add ordered connection list to each connection state
			foreach (IConnection consumerConnection in consumerConnectionPoint.Connections)
			{
				CachedConnectionState curCachedConnectionState = GetCachedConnectionState(connection);
				curCachedConnectionState.OrderedConnections = orderedConnections;
			}

			// Return the ordered connection list
			return orderedConnections;
		}

		protected static void CacheOrderedLinkMaps(IConnection connection)
		{
			CachedConnectionState cachedConnectionState = GetCachedConnectionState(connection);

			// Compute to and from link maps
			int totalLinks = 0;
			foreach (IConnection cxn in connection.SourceConnectionPoint.Connections)
				totalLinks += GetConsumerConnectionSize(cxn);
			int[] toOrderedLinkMap = new int[totalLinks];
			int[] fromOrderedLinkMap = new int[totalLinks];
			IEnumerable orderedConnectionList = GetOrderedConnections(connection);
			int orderedIndex = 0;
			foreach (IConnection orderedConnection in orderedConnectionList)
			{
				int unorderedIndex = 0;
				foreach (IConnection unorderedConnection in connection.SourceConnectionPoint.Connections)
				{
					if (orderedConnection.Id == unorderedConnection.Id)
					{
						for (int i = 0; i < GetConsumerConnectionSize(unorderedConnection); i++)
						{
							toOrderedLinkMap[unorderedIndex] = orderedIndex;
							fromOrderedLinkMap[orderedIndex] = unorderedIndex;

							unorderedIndex++;
							orderedIndex++;
						}
					}
					else
						unorderedIndex += GetConsumerConnectionSize(unorderedConnection);
				}
			}

			// Add to and from link maps to all related connections
			IConnectionPoint consumerConnectionPoint = connection.SourceConnectionPoint;
			foreach (IConnection consumerConnection in consumerConnectionPoint.Connections)
			{
				CachedConnectionState curCachedConnectionState = GetCachedConnectionState(connection);
				curCachedConnectionState.ToOrderedLinkMap = toOrderedLinkMap;
				curCachedConnectionState.FromOrderedLinkMap = fromOrderedLinkMap;
			}
		}

		/// <summary>
		/// Gets the ordered link map for the connection point owning this connection.
		/// </summary>
		/// <param name="connection">Connection to get link map for</param>
		/// <returns>Link map for connection point owning specified connection</returns>
		public static int OrderedLinkFromUnorderedLink(IConnection connection, int unorderedLink)
		{
			// Check to see if the ordered connection list was cached previously
			CachedConnectionState cachedConnectionState = GetCachedConnectionState(connection);
			if (!cachedConnectionState.OrderedLinkMapsAreCached)
				CacheOrderedLinkMaps(connection);

			return cachedConnectionState.ToOrderedLinkMap[unorderedLink];
		}

		/// <summary>
		/// Gets the ordered link map for the connection point owning this connection.
		/// </summary>
		/// <param name="connection">Connection to get link map for</param>
		/// <returns>Link map for connection point owning specified connection</returns>
		public static int UnorderedLinkFromOrderedLink(IConnection connection, int orderedLink)
		{
			// Check to see if the ordered connection list was cached previously
			CachedConnectionState cachedConnectionState = GetCachedConnectionState(connection);
			if (!cachedConnectionState.OrderedLinkMapsAreCached)
				CacheOrderedLinkMaps(connection);

			return cachedConnectionState.FromOrderedLinkMap[orderedLink];
		}

		/// <summary>
		/// Converts from a within-connection number to an absolute link number.
		/// </summary>
		/// <param name="connection">Connection that link belongs to</param>
		/// <param name="relativeLinkNum">Link number within a connection</param>
		/// <returns>The absolute connection number</returns>
		public static int ConvertRelativeToAbsoluteProducerLinkNum(IConnection connection, int relativeProducerLinkNum)
		{
			// Find producer connection point
			IConnectionPoint producerConnectionPoint = connection.TargetConnectionPoint;

            CachedConnectionState cachedConnectionState = GetCachedConnectionState(connection);

            int startLinkNum;
            if (cachedConnectionState.ProducerStartLinkIsCached)
            {
                startLinkNum = cachedConnectionState.ProducerStartLink;
            }
            else
            {
                // Count links to start of given connection
                startLinkNum = 0;
                foreach (IConnection cxn in producerConnectionPoint.Connections)
                {
                    if (cxn.Id == connection.Id) break;

                    startLinkNum += ConnectionHelper.GetProducerConnectionSize(cxn);
                }
                cachedConnectionState.ProducerStartLink = startLinkNum;
            }

			return startLinkNum + relativeProducerLinkNum;
		}

		/// <summary>
		/// Converts from a within-connection number to an absolute link number.
		/// </summary>
		/// <param name="connection">Connection that link belongs to</param>
		/// <param name="relativeLinkNum">Link number within a connection</param>
		/// <returns>The absolute connection number</returns>
		public static int ConvertRelativeToAbsoluteConsumerLinkNum(IConnection connection, int relativeConsumerLinkNum)
		{
			// Find consumer connection point
			IConnectionPoint consumerConnectionPoint = connection.SourceConnectionPoint;

            CachedConnectionState cachedConnectionState = GetCachedConnectionState(connection);

            int startLinkNum;
            if (cachedConnectionState.ConsumerStartLinkIsCached)
            {
                startLinkNum = cachedConnectionState.ConsumerStartLink;
            }
            else
            {
                // Count links to start of given connection
                startLinkNum = 0;
                foreach (IConnection cxn in consumerConnectionPoint.Connections)
                {
                    if (cxn.Id == connection.Id) break;

                    startLinkNum += ConnectionHelper.GetConsumerConnectionSize(cxn);
                }
                cachedConnectionState.ConsumerStartLink = startLinkNum;
            }

			return startLinkNum + relativeConsumerLinkNum;
		}

        /// <summary>
        /// Gets the cached corresponding connection for the specified connection.
        /// </summary>
        /// <param name="connection">Connection to retrieve corresponding connection for</param>
        /// <param name="relayExternal">Specifies whether the connection relays externally</param>
        /// <returns>Connection corresponding to the supplied connection, or null if no connection set</returns>
        public static IConnection GetCorrespondingConnection(IConnection connection, out bool relayExternal)
        {
            CachedConnectionState cachedConnectionState = GetCachedConnectionState(connection);

            relayExternal = cachedConnectionState.RelayExternal;
            return cachedConnectionState.CorrespondingConnection;
        }

        /// <summary>
        /// Sets the cached corresponding connection for the specified connection.
        /// </summary>
        /// <param name="connection">Connection to set corresponding connection for</param>
        /// <param name="correspondingConnection">Corresponding connection to associate with the given connection</param>
        /// <param name="relayExternal">Specifies whether the connection relays externally</param>
        public static void SetCorrespondingConnection(IConnection connection, IConnection correspondingConnection, bool relayExternal)
        {
            CachedConnectionState cachedConnectionState = GetCachedConnectionState(connection);

            cachedConnectionState.CorrespondingConnection = correspondingConnection;
            cachedConnectionState.RelayExternal = relayExternal;
        }

        /// <summary>
        /// Gets the cached mapping class, creating one if required.
        /// </summary>
        /// <param name="connection"></param>
        /// <returns></returns>
        public static ProducerConsumer.IConnectionMapping GetMappingClass(IConnection connection)
        {
            CachedConnectionState cachedConnectionState = GetCachedConnectionState(connection);

            if (cachedConnectionState.ConnectionMappingIsCached)
                return cachedConnectionState.ConnectionMapping;

            ProducerConsumer.IConnectionMapping connectionMapping = null;
            ConnectionProperty mappingProperty = connection.GetProperty("Mapping") as ConnectionProperty;
            if (mappingProperty != null && mappingProperty.Value is Type)
            {
                object mappingClass = Activator.CreateInstance((Type)mappingProperty.Value);
                if (mappingClass is ProducerConsumer.IConnectionMapping)
                    connectionMapping = mappingClass as ProducerConsumer.IConnectionMapping;
            }
            if (connectionMapping == null)
                connectionMapping = m_DefaultMapping;

            int[, ,] consumerLinkMap;
            int[, ,] producerLinkMap;
            GenerateLinkMaps(connection, connectionMapping, out consumerLinkMap, out producerLinkMap);
            cachedConnectionState.ConsumerLinkMap = consumerLinkMap;
            cachedConnectionState.ProducerLinkMap = producerLinkMap;

            cachedConnectionState.ConnectionMapping = connectionMapping;

            return connectionMapping;
        }

        /// <summary>
        /// Generates producer and consumer link maps for a given connection.
        /// </summary>
        /// <param name="connection">Connection to generate link maps for</param>
        /// <param name="consumerLinkMap">Consumer link map</param>
        /// <param name="producerLinkMap">Producer link map</param>
        private static void GenerateLinkMaps(IConnection connection, ProducerConsumer.IConnectionMapping connectionMapping, out int[,,] consumerLinkMap, out int[,,] producerLinkMap)
        {
            IConnectionPoint consumerPoint = connection.SourceConnectionPoint;
            IConnectionPoint producerPoint = connection.TargetConnectionPoint;

            // Get the parent dimensions
            int[] parentDims = DimensionHelper.GetTotalDimsFromTask(connection.ParentTask);
            int numParentElems = DimensionHelper.GetNumElementsFromDimensions(parentDims);

            // Get the dimensions of the producer task and calculate the number of elements
            int[] producerTaskTotalDims = DimensionHelper.GetTotalDimsFromTask(producerPoint.ParentTask);
            int[] producerTaskDims = DimensionHelper.RemoveMostSignificantDims(producerTaskTotalDims, parentDims);
            int numProducerElems = DimensionHelper.GetNumElementsFromDimensions(producerTaskDims);

            // Get the dimensions of the consumer task and calculate the number of elements
            int[] consumerTaskTotalDims = DimensionHelper.GetTotalDimsFromTask(consumerPoint.ParentTask);
            int[] consumerTaskDims = DimensionHelper.RemoveMostSignificantDims(consumerTaskTotalDims, parentDims);
            int numConsumerElems = DimensionHelper.GetNumElementsFromDimensions(consumerTaskDims);

            // Calculate the total number of elements
            int numTotalConsumerElems = numConsumerElems * numParentElems;
            int numTotalProducerElems = numProducerElems * numParentElems;

            // Get the dimensions of the connection and calculate the number of links
            connectionMapping.Initialize(connection, consumerTaskDims, producerTaskDims);
            int[] consumerCxnDims = connectionMapping.GetConsumerConnectionDimensions();
            int numConsumerLinks = DimensionHelper.GetNumElementsFromDimensions(consumerCxnDims);
            int[] producerCxnDims = connectionMapping.GetProducerConnectionDimensions();
            int numProducerLinks = DimensionHelper.GetNumElementsFromDimensions(producerCxnDims);

            // Populate the consumer and producer link maps
            consumerLinkMap = new int[numTotalConsumerElems, numConsumerLinks, 2];
            producerLinkMap = new int[numTotalProducerElems, numProducerLinks, 2];
            for (int i = 0; i < numParentElems; i++)
            {
                int consumerStartElem = i * numConsumerElems;
                int producerStartElem = i * numProducerElems;
                for (int consumerElem = 0; consumerElem < numConsumerElems; consumerElem++)
                {
                    for (int consumerLink = 0; consumerLink < numConsumerLinks; consumerLink++)
                    {
                        int totalConsumerElem = consumerStartElem + consumerElem;
                        int totalProducerElem, producerLink;
                        connectionMapping.MapConsumerToProducer(consumerElem, consumerLink, out totalProducerElem, out producerLink);
                        totalProducerElem += producerStartElem;

                        consumerLinkMap[totalConsumerElem, consumerLink, 0] = totalProducerElem;
                        consumerLinkMap[totalConsumerElem, consumerLink, 1] = producerLink;

                        producerLinkMap[totalProducerElem, producerLink, 0] = totalConsumerElem;
                        producerLinkMap[totalProducerElem, producerLink, 1] = consumerLink;
                    }
                }
            }
        }

        /// <summary>
        /// Finds the producer link number connected to the given consumer link number
        /// for the specified connection by using the mapping provided by the connection.
        /// </summary>
        /// <param name="connection">Connection that owns the link</param>
        /// <param name="consumerElemNum">Element number at the consumer end</param>
        /// <param name="consumerLinkNum">Consumer link number within the given connection</param>
        /// <param name="producerElemNum">Element number at the producer end</param>
        /// <param name="producerLinkNum">Producer link number within the given connection</param>
        public static void MapConsumerToProducer(IConnection connection, int consumerElemNum, int consumerLinkNum, out int producerElemNum, out int producerLinkNum)
        {
            CachedConnectionState cachedConnectionState = GetCachedConnectionState(connection);

            if (!cachedConnectionState.ConsumerLinkMapIsCached)
            {
                GetMappingClass(connection);
            }

            producerElemNum = cachedConnectionState.ConsumerLinkMap[consumerElemNum, consumerLinkNum, 0];
            producerLinkNum = cachedConnectionState.ConsumerLinkMap[consumerElemNum, consumerLinkNum, 1];
        }

        /// <summary>
        /// Finds the consumer link number connected to the given producer link number
        /// for the specified connection by using the mapping provided by the connection.
        /// </summary>
        /// <param name="connection">Connection that owns the link</param>
        /// <param name="consumerElemNum">Element number at the producer end</param>
        /// <param name="consumerLinkNum">Producer link number within the given connection</param>
        /// <param name="producerElemNum">Element number at the consumer end</param>
        /// <param name="producerLinkNum">Consumer link number within the given connection</param>
        public static void MapProducerToConsumer(IConnection connection, int producerElemNum, int producerLinkNum, out int consumerElemNum, out int consumerLinkNum)
        {
            CachedConnectionState cachedConnectionState = GetCachedConnectionState(connection);

            if (!cachedConnectionState.ProducerLinkMapIsCached)
            {
                GetMappingClass(connection);
            }

            consumerElemNum = cachedConnectionState.ProducerLinkMap[producerElemNum, producerLinkNum, 0];
            consumerLinkNum = cachedConnectionState.ProducerLinkMap[producerElemNum, producerLinkNum, 1];
        }

        /// <summary>
        /// Gets the starting link number for the specified connection.
        /// </summary>
        /// <param name="consumerConnection">Consumer connection to find starting link number for</param>
        /// <returns>Starting link number for given connection</returns>
        public static int GetStartLinkNumberForConsumerConnection(IConnection consumerConnection, IConnectionPoint consumerConnectionPoint)
        {
            CachedConnectionState cachedConnectionState = GetCachedConnectionState(consumerConnection);

            int curLinkNum = 0;
            if (cachedConnectionState.ConsumerStartLinkIsCached)
            {
                curLinkNum = cachedConnectionState.ConsumerStartLink;
            }
            else
            {
                // Iterate over connection list adding link counts until
                // the given connection is found and then return the accumulated
                // link count
                foreach (IConnection connection in consumerConnectionPoint.Connections)
                {
                    if (connection == consumerConnection)
                        break;

                    curLinkNum += GetConsumerConnectionSize(connection);
                }

                cachedConnectionState.ConsumerStartLink = curLinkNum;
            }

            return curLinkNum;
        }

        /// <summary>
        /// Gets the starting link number for the specified connection.
        /// </summary>
        /// <param name="producerConnection">Producer connection to find starting link number for</param>
        /// <returns>Starting link number for given connection</returns>
        public static int GetStartLinkNumberForProducerConnection(IConnection producerConnection, IConnectionPoint producerConnectionPoint)
        {
            CachedConnectionState cachedConnectionState = GetCachedConnectionState(producerConnection);

            int curLinkNum = 0;
            if (cachedConnectionState.ProducerStartLinkIsCached)
            {
                curLinkNum = cachedConnectionState.ProducerStartLink;
            }
            else
            {
                // Iterate over connection list adding link counts until
                // the given connection is found and then return the accumulated
                // link count
                foreach (IConnection connection in producerConnectionPoint.Connections)
                {
                    if (connection == producerConnection)
                        break;

                    curLinkNum += GetProducerConnectionSize(connection);
                }

                cachedConnectionState.ProducerStartLink = curLinkNum;
            }

            return curLinkNum;
        }

        /// <summary>
        /// Gets whether the specified consumer link on the specified element is disabled.
        /// </summary>
        /// <param name="connection">Connection on the connection point</param>
        /// <param name="consumerElemNum">Consumer element number</param>
        /// <param name="consumerLinkNum">Consumer link number</param>
        /// <returns>true if link is disabled, otherwise false</returns>
        public static bool IsConsumerLinkDisabled(IConnection connection, int consumerElemNum, int consumerLinkNum)
        {
            // Find connection that this link is part of
            IConnection curConnection = null;
            int curStartLink = -1;
            foreach (IConnection cxn in connection.SourceConnectionPoint.Connections)
            {
                int startLink = GetStartLinkNumberForConsumerConnection(cxn, cxn.SourceConnectionPoint);
                if (startLink > consumerLinkNum) break;
                curConnection = cxn;
                curStartLink = startLink;
            }

            // Get mapping
            ProducerConsumer.IConnectionMapping mapping = GetMappingClass(curConnection);

            // If the mapping specifies no disabled links then this link is not disabled
            if (!mapping.HasDisabledLinks()) return false;

            // Calculate relative link
            int consumerRelLinkNum = consumerLinkNum - curStartLink;

            // Return whether link is disabled
            return mapping.IsConsumerLinkDisabled(consumerElemNum, consumerRelLinkNum);
        }

        /// <summary>
        /// Gets whether the specified producer link on the specified element is disabled.
        /// </summary>
        /// <param name="connection">Connection on the connection point</param>
        /// <param name="producerElemNum">Producer element number</param>
        /// <param name="producerLinkNum">Producer link number</param>
        /// <returns>true if link is disabled, otherwise false</returns>
        public static bool IsProducerLinkDisabled(IConnection connection, int producerElemNum, int producerLinkNum)
        {
            // Find connection that this link is part of
            IConnection curConnection = null;
            int curStartLink = -1;
            foreach (IConnection cxn in connection.TargetConnectionPoint.Connections)
            {
                int startLink = GetStartLinkNumberForProducerConnection(cxn, cxn.TargetConnectionPoint);
                if (startLink > producerLinkNum) break;
                curConnection = cxn;
                curStartLink = startLink;
            }

            // Get mapping
            ProducerConsumer.IConnectionMapping mapping = GetMappingClass(curConnection);

            // If the mapping specifies no disabled links then this link is not disabled
            if (!mapping.HasDisabledLinks()) return false;

            // Calculate relative link
            int producerRelLinkNum = producerLinkNum - curStartLink;

            // Return whether link is disabled
            return mapping.IsProducerLinkDisabled(producerElemNum, producerRelLinkNum);
        }
    }
}
